<script setup>
import { ref, computed, watch, nextTick, onUnmounted } from 'vue';
import { Icon } from '@iconify/vue';
import { useI18n } from 'vue-i18n';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import { storeToRefs } from 'pinia';
import { useWorkspaceStore } from '../../stores/workspace';
// modules/editor has been removed; use invoke RPC calls for file operations
// import { FileManager } from '../../modules/editor/services/FileManager';
// import { readTextFile } from '@tauri-apps/plugin-fs';
// import { ref } from 'vue'; // duplicate import removed

const props = defineProps({
  include: { type: String, default: '' },
  exclude: { type: String, default: 'node_modules, .git, dist, build, target' },
});

const emit = defineEmits(['open-file']);
const { t } = useI18n();

const query = ref('');
const replaceText = ref('');
const showReplace = ref(false);
const matchCase = ref(false);
const matchWholeWord = ref(false);
const useRegex = ref(false);

const includeGlobs = ref(props.include);
const excludeGlobs = ref(props.exclude);

const isSearching = ref(false);
const cancelled = ref(false);
const searchProgress = ref({ current: 0, total: 0 });
const searchHistory = ref([]);
const expandedFiles = ref(new Set());
const searchTimeout = ref(null);
const searchInput = ref(null);

// 搜索状态管理
let currentSearchId = null;
let searchUnlisten = null;
let searchDoneUnlisten = null;

const results = ref([]); // [{ path, name, matches: [{ line, col, preview, match }], expanded: boolean }]
const totalMatches = computed(() => results.value.reduce((s, f) => s + (f.matches?.length || 0), 0));
const totalFiles = computed(() => results.value.length);

// 将绝对路径转为相对工作区的路径，贴近 VS Code 展示
const workspaceStore = useWorkspaceStore();
const { workspaceRoot } = storeToRefs(workspaceStore);

function toRelativePath(absPath) {
  const root = workspaceRoot.value || '';
  const normAbs = (absPath || '').replace(/\\/g, '/');
  const normRoot = (root || '').replace(/\\/g, '/');
  if (normRoot && normAbs.toLowerCase().startsWith(normRoot.toLowerCase() + '/')) {
    return normAbs.slice(normRoot.length + 1);
  }
  return absPath;
}

// 自然排序（忽略大小写，路径使用正斜杠）
function pathCompare(a, b) {
  const pa = (a || '').replace(/\\/g, '/').toLowerCase();
  const pb = (b || '').replace(/\\/g, '/').toLowerCase();
  return pa.localeCompare(pb);
}

// 防抖搜索
function debouncedSearch() {
  if (searchTimeout.value) {
    clearTimeout(searchTimeout.value);
  }
  searchTimeout.value = setTimeout(() => {
    runSearch();
  }, 300);
}

// 实时搜索监听
watch(query, (newQuery) => {
  if (newQuery.trim()) {
    debouncedSearch();
  } else {
    results.value = [];
    cancelSearch();
  }
});

// 监听搜索选项变化
watch([matchCase, matchWholeWord, useRegex, includeGlobs, excludeGlobs], () => {
  if (query.value.trim()) {
    debouncedSearch();
  }
});

async function runSearch() {
  // 重置替换统计
  replaceSummary.value = null;
  const root = workspaceRoot.value;
  if (!root || !query.value.trim()) { 
    results.value = []; 
    return; 
  }
  
  // 取消之前的搜索
  await cancelSearch();
  
  // 添加到搜索历史
  const queryText = query.value.trim();
  if (queryText && !searchHistory.value.includes(queryText)) {
    searchHistory.value.unshift(queryText);
    if (searchHistory.value.length > 10) {
      searchHistory.value = searchHistory.value.slice(0, 10);
    }
  }
  
  // 新一轮搜索重置展开状态，贴近 VS Code 行为
  expandedFiles.value.clear();
  results.value = [];
  isSearching.value = true;
  cancelled.value = false;
  
  try {
    // 调用后端流式搜索
    const searchId = await invoke('start_content_search', {
      args: {
        directory: root,
        query: queryText,
        is_regex: useRegex.value,
        case_sensitive: matchCase.value,
        whole_word: matchWholeWord.value,
        include_pattern: includeGlobs.value || undefined,
        exclude_pattern: excludeGlobs.value || undefined,
        max_results: 1000,
        max_file_size: 2 * 1024 * 1024 // 2MB
      }
    });
    
    currentSearchId = searchId;
    
    // 监听搜索结果
    searchUnlisten = await listen(`search:results:${searchId}`, (event) => {
      if (cancelled.value) return;
      
      const matches = event.payload;
      if (Array.isArray(matches)) {
        // 按文件分组结果
        const fileMap = new Map();
        
        // 保留现有结果
        results.value.forEach(file => {
          fileMap.set(file.path, file);
        });
        
        // 添加新结果
        matches.forEach(match => {
          if (!fileMap.has(match.path)) {
            const name = match.path.split(/[\\\/]/).pop() || match.path;
            fileMap.set(match.path, {
              path: match.path,
              name,
              relPath: toRelativePath(match.path),
              matches: [],
            });
          }
          
          const file = fileMap.get(match.path);
          // 转换匹配格式（兼容单行与多行）
          const sub = match.submatches?.[0];
          const start = sub?.start ?? 0;
          const end = sub?.end ?? start;
          const firstLine = match.line ?? '';
          const preview = {
            prefix: firstLine.substring(0, start),
            hit: firstLine.substring(start, end),
            suffix: firstLine.substring(end)
          };
          // 多行预览（取后续最多两行，使用后端提供的 preview_lines）
          const previewLines = Array.isArray(match.preview_lines) ? match.preview_lines : [];
          const isMultiline = (match.end_line && match.start_line && match.end_line > match.start_line) ||
                              (!!match.text && match.text.includes('\n')) ||
                              (previewLines.length > 1);
          const extraPreview = isMultiline ? previewLines.slice(1, 3).join(' ⏎ ') : '';
          const extraLinesCount = isMultiline ? Math.max(0, previewLines.length - 1) : 0;
          
          file.matches.push({
            line: match.line_number,
            col: start + 1,
            preview,
            match: preview.hit,
            // 新增用于显示多行预览的信息
            isMultiline,
            extraPreview,
            extraLinesCount,
            // 新增：用于跨行高亮的范围信息（1-based）
            startLine: (Number(match.start_line) || Number(match.line_number) || 1),
            startCol: (Number(start) + 1),
            endLine: (() => {
              if (Number(match.end_line)) return Number(match.end_line);
              const text = String(match.text || '');
              const nl = (text.match(/\n/g) || []).length;
              return (Number(match.start_line) || Number(match.line_number) || 1) + nl;
            })(),
            endCol: (() => {
              if (Number(match.end_col)) return Number(match.end_col);
              const text = String(match.text || '');
              const nlParts = text.split('\n');
              if (nlParts.length > 1) {
                const last = nlParts[nlParts.length - 1] || '';
                return last.length + 1; // 1-based
              }
              const end0 = Number(end);
              return (isFinite(end0) ? end0 : start) + 1; // 1-based
            })(),
          });
        });
        
        // 每次批量更新后排序，使其贴近 VS Code：文件按路径、匹配按行列
        const newResults = Array.from(fileMap.values())
          .map(f => ({
            ...f,
            matches: [...f.matches].sort((a, b) => a.line === b.line ? a.col - b.col : a.line - b.line)
          }))
          .sort((a, b) => pathCompare(a.path, b.path));
        
        results.value = newResults;
      }
    });
    
    // 监听搜索完成
    searchDoneUnlisten = await listen(`search:done:${searchId}`, (event) => {
      isSearching.value = false;
      const payload = event.payload;
      if (payload?.cancelled) {
        cancelled.value = true;
      }
      // 清理监听器
      cleanupSearch();
    });
    
  } catch (error) {
    console.error('搜索失败:', error);
    isSearching.value = false;
  }
}

async function cancelSearch() {
  cancelled.value = true;
  if (searchTimeout.value) {
    clearTimeout(searchTimeout.value);
    searchTimeout.value = null;
  }
  
  if (currentSearchId) {
    try {
      await invoke('cancel_content_search', { args: { id: currentSearchId } });
    } catch (error) {
      console.error('取消搜索失败:', error);
    }
  }
  
  cleanupSearch();
}

function cleanupSearch() {
  if (searchUnlisten) {
    searchUnlisten();
    searchUnlisten = null;
  }
  if (searchDoneUnlisten) {
    searchDoneUnlisten();
    searchDoneUnlisten = null;
  }
  currentSearchId = null;
}

function clearQuery() {
  query.value = '';
  results.value = [];
  expandedFiles.value.clear();
  cancelSearch();
}

// 处理搜索输入框键盘：Enter 执行搜索，Shift+Enter 换行
function onSearchKeydown(e) {
  if (e.key === 'Enter' && !e.shiftKey) {
    e.preventDefault();
    runSearch();
  }
}

// 组件卸载时清理
onUnmounted(() => {
  cancelSearch();
});

// 切换文件展开状态
function toggleFileExpanded(filePath) {
  if (expandedFiles.value.has(filePath)) {
    expandedFiles.value.delete(filePath);
  } else {
    expandedFiles.value.add(filePath);
  }
}

// 展开所有文件
function expandAll() {
  results.value.forEach(file => {
    expandedFiles.value.add(file.path);
  });
}

// 折叠所有文件
function collapseAll() {
  expandedFiles.value.clear();
}

// 打开文件并跳转到匹配位置
function onOpen(file, match) {
  emit('open-file', {
    path: file.path,
    line: match.line,
    column: match.col,
    // 新增：跨行高亮范围
    startLine: match.startLine,
    startCol: match.startCol,
    endLine: match.endLine,
    endCol: match.endCol,
  });
}

// 自动调整文本框高度
function adjustTextareaHeight() {
  nextTick(() => {
    const textarea = searchInput.value;
    if (textarea) {
      textarea.style.height = 'auto';
      const scrollHeight = textarea.scrollHeight;
      const maxHeight = 120; // max-h-[120px]
      textarea.style.height = Math.min(scrollHeight, maxHeight) + 'px';
    }
  });
}

// 新增：替换运行状态与统计
const replacing = ref(false);
const replaceSummary = ref(null);

// 构建搜索正则
function buildSearchRegex() {
  const q = String(query.value || '');
  if (!q) return null;
  const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  let pattern = '';
  if (useRegex.value) {
    pattern = q;
  } else {
    pattern = escapeRegExp(q);
  }
  if (matchWholeWord.value) {
    pattern = `\\b${pattern}\\b`;
  }
  let flags = 'gmsu';
  if (!matchCase.value) flags += 'i';
  try {
    return new RegExp(pattern, flags);
  } catch (e) {
    console.error('Invalid regex:', e);
    return null;
  }
}

// 替换所有匹配（跨文件）- 使用 invoke RPC 替代 FileManager
async function onReplaceAll() {
  if (replacing.value) return;
  const re = buildSearchRegex();
  if (!re) return;
  
  let filesChanged = 0;
  let matchesChanged = 0;
  replacing.value = true;
  try {
    // 若无结果，尝试先触发一次搜索
    if (!results.value.length) {
      await runSearch();
    }

    for (const file of results.value) {
      try {
        const path = file.path;
        // 使用 invoke RPC 读取文件内容
        const original = await invoke('fs_read_text', { path });
        let replacedInFile = 0;
        const newContent = original.replace(re, (...args) => {
          // 统计次数：当使用全局正则时，每次匹配都会调用
          replacedInFile += 1;
          return String(replaceText.value ?? '');
        });
        if (replacedInFile > 0 && newContent !== original) {
          // 使用 invoke RPC 写入文件内容
          await invoke('fs_write_text', { path, content: newContent, createDirs: true });
          filesChanged += 1;
          matchesChanged += replacedInFile;
        }
      } catch (err) {
        console.error('Replace failed for file:', file.path, err);
      }
    }

    replaceSummary.value = { files: filesChanged, matches: matchesChanged };
    // 替换完成后刷新搜索结果，便于用户确认
    if (query.value.trim()) {
      await runSearch();
    }
  } finally {
    replacing.value = false;
  }
}
</script>

<template>
  <div class="flex h-full min-h-0 flex-col gap-2 px-2 py-2">
    <div class="flex items-center gap-1">
      <div class="relative flex-1">
        <textarea
          v-model="query"
          rows="1"
          class="w-full rounded border pl-2 pr-20 py-1 text-xs bg-white dark:bg-neutral-900 border-slate-200 dark:border-neutral-800 outline-none focus:ring-1 focus:ring-blue-400 resize-none leading-tight min-h-[28px] max-h-[120px] overflow-y-auto"
          :placeholder="t('search.search_placeholder')"
          @keydown="onSearchKeydown"
          @input="adjustTextareaHeight"
          ref="searchInput"
        />
        <div class="absolute top-1 right-1 flex items-center gap-1">
          <button
            class="h-6 w-6 inline-flex items-center justify-center rounded hover:bg-slate-100 dark:hover:bg-neutral-800"
            :class="matchCase ? 'text-blue-600 dark:text-blue-400' : 'text-slate-500 dark:text-neutral-400'"
            :aria-pressed="String(matchCase)"
            :title="t('search.match_case')"
            @click="matchCase = !matchCase"
          >
            <Icon icon="codicon:case-sensitive" width="14" height="14" />
          </button>
          <button
            class="h-6 w-6 inline-flex items-center justify-center rounded hover:bg-slate-100 dark:hover:bg-neutral-800"
            :class="matchWholeWord ? 'text-blue-600 dark:text-blue-400' : 'text-slate-500 dark:text-neutral-400'"
            :aria-pressed="String(matchWholeWord)"
            :title="t('search.whole_word')"
            @click="matchWholeWord = !matchWholeWord"
          >
            <Icon icon="codicon:whole-word" width="14" height="14" />
          </button>
          <button
            class="h-6 w-6 inline-flex items-center justify-center rounded hover:bg-slate-100 dark:hover:bg-neutral-800"
            :class="useRegex ? 'text-blue-600 dark:text-blue-400' : 'text-slate-500 dark:text-neutral-400'"
            :aria-pressed="String(useRegex)"
            :title="t('search.regex')"
            @click="useRegex = !useRegex"
          >
            <Icon icon="codicon:regex" width="14" height="14" />
          </button>
        </div>
      </div>
      <button class="h-7 w-7 inline-flex items-center justify-center rounded hover:bg-slate-100 dark:hover:bg-neutral-800 disabled:opacity-50" :disabled="isSearching || !query" @click="runSearch" :title="t('search.search')">
        <Icon icon="codicon:search" width="16" height="16" />
      </button>
      <button v-if="!showReplace" class="h-7 w-7 inline-flex items-center justify-center rounded hover:bg-slate-100 dark:hover:bg-neutral-800" @click="showReplace=true" :title="t('search.toggle_replace')">
        <Icon icon="codicon:replace" width="16" height="16" />
      </button>
    </div>

    <!-- 替换输入框 -->
    <div v-if="showReplace" class="flex items-center gap-1">
      <div class="relative flex-1">
        <input
          v-model="replaceText"
          type="text"
          class="w-full rounded border pl-2 pr-8 py-1 text-xs bg-white dark:bg-neutral-900 border-slate-200 dark:border-neutral-800 outline-none focus:ring-1 focus:ring-blue-400"
          :placeholder="t('search.replace_placeholder')"
        />
      </div>
      <!-- 新增：替换所有按钮 -->
      <button
        class="h-7 w-7 inline-flex items-center justify-center rounded hover:bg-slate-100 dark:hover:bg-neutral-800 disabled:opacity-50"
        :disabled="isSearching || replacing || !query || replaceText === ''"
        @click="onReplaceAll"
        :title="t('search.replace_all')"
      >
        <Icon icon="codicon:replace-all" width="16" height="16" />
      </button>
      <button class="h-7 w-7 inline-flex items-center justify-center rounded hover:bg-slate-100 dark:hover:bg-neutral-800" @click="showReplace=false" :title="t('search.hide_replace')">
        <Icon icon="codicon:chevron-up" width="16" height="16" />
      </button>
    </div>

    <!-- 搜索选项 -->
    <div class="space-y-2 text-xs">
      <div>
        <div class="mb-1 text-slate-500 dark:text-neutral-400">{{ t('search.include') }}</div>
        <input v-model="includeGlobs" type="text" class="w-full rounded border px-2 py-1 text-xs bg-white dark:bg-neutral-900 border-slate-200 dark:border-neutral-800" :placeholder="t('search.include_hint')" />
      </div>
      <div>
        <div class="mb-1 text-slate-500 dark:text-neutral-400">{{ t('search.exclude') }}</div>
        <input v-model="excludeGlobs" type="text" class="w-full rounded border px-2 py-1 text-xs bg-white dark:bg-neutral-900 border-slate-200 dark:border-neutral-800" :placeholder="t('search.exclude_hint')" />
      </div>
    </div>

    <div class="min-h-0 flex-1 overflow-auto thin-scrollbar border-t pt-2 dark:border-neutral-800 rounded-sm">
      <!-- 搜索进度指示 -->
      <div v-if="isSearching" class="text-xs px-1 mb-2 text-slate-500 dark:text-neutral-400 flex items-center gap-1">
        <Icon icon="codicon:loading" class="animate-spin" width="14" height="14" />
        <span>{{ t('search.searching') }}</span>
      </div>

      <!-- 替换结果摘要 -->
      <div v-if="replaceSummary" class="text-xs px-1 mb-1 text-green-600 dark:text-green-400">
        {{ t('search.replace_done', { files: replaceSummary.files, matches: replaceSummary.matches }) }}
      </div>
      
      <!-- 搜索结果摘要 -->
      <div v-if="query && !isSearching" class="flex items-center justify-between text-xs px-1 mb-2">
        <span class="text-slate-600 dark:text-neutral-400">
          {{ t('search.results_summary', { total: totalMatches, files: results.length }) }}
        </span>
        <div v-if="results.length > 0" class="flex gap-1">
          <button @click="expandAll" class="text-slate-500 hover:text-slate-700 dark:text-neutral-400 dark:hover:text-neutral-200" :title="t('search.expand_all')">
            <Icon icon="codicon:expand-all" width="12" height="12" />
          </button>
          <button @click="collapseAll" class="text-slate-500 hover:text-slate-700 dark:text-neutral-400 dark:hover:text-neutral-200" :title="t('search.collapse_all')">
            <Icon icon="codicon:collapse-all" width="12" height="12" />
          </button>
          <button @click="clearQuery" class="text-slate-500 hover:text-slate-700 dark:text-neutral-400 dark:hover:text-neutral-200" :title="t('search.clear_results')">
            <Icon icon="codicon:clear-all" width="12" height="12" />
          </button>
        </div>
      </div>
      
      <!-- 搜索结果列表 -->
      <div v-for="file in results" :key="file.path" class="mb-1">
        <!-- 文件头部 -->
        <div class="flex items-center justify-between text-[12px] px-1 py-1 font-medium text-slate-700 dark:text-neutral-300 hover:bg-slate-50 dark:hover:bg-neutral-800/50 cursor-pointer rounded"
             @click="toggleFileExpanded(file.path)">
          <div class="flex items-center gap-1 truncate">
            <Icon :icon="expandedFiles.has(file.path) ? 'codicon:chevron-down' : 'codicon:chevron-right'" width="12" height="12" class="text-slate-400" />
            <span class="truncate">{{ file.name }}</span>
            <span class="text-slate-400 dark:text-neutral-500">({{ file.matches.length }})</span>
          </div>
          <div class="text-[11px] text-slate-400 dark:text-neutral-500 truncate max-w-[50%]">{{ file.relPath || file.path }}</div>
        </div>
        
        <!-- 匹配项列表 -->
        <div v-if="expandedFiles.has(file.path)" class="ml-4">
          <div v-for="m in file.matches" :key="file.path + ':' + m.line + ':' + m.col" 
               class="flex items-center group px-2 py-0.5 text-[12px] hover:bg-slate-50 dark:hover:bg-neutral-800/80 cursor-pointer rounded"
               @click="onOpen(file, m)">
            <div class="flex-1">
              <span class="text-slate-400 dark:text-neutral-500 mr-2">{{ m.line }}:{{ m.col }}</span>
              <span class="text-slate-700 dark:text-neutral-300">
                <span>{{ m.preview.prefix }}</span>
                <span class="bg-yellow-200/60 dark:bg-yellow-800/50 px-0.5 rounded">{{ m.preview.hit }}</span>
                <span>{{ m.preview.suffix }}</span>
                <!-- 多行额外预览（最多两行，使用 ⏎ 分隔） -->
                <span v-if="m.isMultiline && m.extraPreview" class="text-slate-400 dark:text-neutral-500">
                  
                  ⏎ {{ m.extraPreview }}<span v-if="m.extraLinesCount > 2"> …</span>
                </span>
              </span>
            </div>
          </div>
        </div>
      </div>
      
      <!-- 无结果提示 -->
      <div v-if="!isSearching && !results.length && query" class="px-2 py-1 text-xs text-slate-400 dark:text-neutral-500">
        {{ t('search.no_results') }}
      </div>
    </div>
  </div>
</template>

<style scoped>
.thin-scrollbar { scrollbar-width: thin; }
</style>



async function searchFile(path) {
  try {
    result.value = await readTextFile(path);
  } catch (e) {
    result.value = '';
  }
}


